﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
using BGShell.Components;
using BGShell.Interop;
using BGShell.PowerShell.Runspaces;
using WindowsFormsAero;

namespace BGShell.PowerShell.Controls
{
    using PSSize = System.Management.Automation.Host.Size;

    [Serializable]
    internal enum ConsoleControlStatus
    {
        None = 0,
        Ready = 1,
        Busy = 2,
        ExitRequested = 3,
    }

    [System.ComponentModel.DesignerCategory("Code")]
    public partial class ConsoleControl : AeroRichEdit
    {
        private PSSize _bufferSize;
        private String _windowTitle;

        private BGShellPipelineProxy _pipelineProxy;
        private IRunspaceProxy _runspaceProxy;

        private ConsoleControlStatus _currentStatus;
        private RunspaceHostInfo _hostInfo;

        private ConsoleControlFormatSettings _formatSettings;
        private ConsoleControlPrivateObject _privateObject;
        private EditControlDataTipProvider _tipProvider;
        
        private OutputWriter _writer;
        private InputReader _reader;

        private KeyboardHook _hook;

        public ConsoleControl()
        {
            AllowDrop = false;
            BorderStyle = BorderStyle.None;
            FormatSettings = new ConsoleControlFormatSettings();
            DetectUrls = false;
            ScrollBars = RichTextBoxScrollBars.ForcedVertical;
            ShortcutsEnabled = false;

            _writer = new OutputWriter(this);
            _tipProvider = new EditControlDataTipProvider(this);
            _privateObject = new ConsoleControlPrivateObject(this);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_reader != null)
                {
                    _reader.Dispose();
                }

                if (_tipProvider != null)
                {
                    _tipProvider.Dispose();
                }

                if (_hook != null)
                {
                    _hook.KeyDown -= OnHookKeyDown;
                    _hook = null;
                }

                _writer.Dispose();

                CloseRunspaceAsync();
            }

            _reader = null;
            _writer = null;
            _tipProvider = null;

            base.Dispose(disposing);
        }

        [Browsable(false)]
        public bool IsBusy
        {
            get { return (CurrentStatus == ConsoleControlStatus.Busy); }
            private set
            {
                if (value != IsBusy)
                {
                    if (value)
                    {
                        CurrentStatus = ConsoleControlStatus.Busy;
                    }
                    else
                    {
                        CurrentStatus = ConsoleControlStatus.Ready;
                    }

                    _writer.Enabled = value;
                    UpdateTipsEnabled();

                    OnIsBusyChanged(EventArgs.Empty);
                }
            }
        }

        [Browsable(false)]
        public bool IsBackground
        {
            get;
            set;
        }

        public string WindowTitle
        {
            get { return _windowTitle; }
            set
            {
                if (_windowTitle != value)
                {
                    _windowTitle = value;
                    OnWindowTitleChanged(EventArgs.Empty);
                }
            }
        }

        public ConsoleControlFormatSettings FormatSettings
        {
            get { return _formatSettings; }
            set
            {
                if ((_formatSettings != value) && (value != null))
                {
                    _formatSettings = value;
                    _formatSettings.Normal.Apply(this);
                }
            }
        }

        public void OpenRunspaceAsync()
        {
            if (!DesignMode && _runspaceProxy == null)
            {
                CurrentStatus = ConsoleControlStatus.None;

                _hostInfo = new RunspaceHostInfo()
                {
                    InstanceID = Guid.NewGuid(),
                    IsBackground = IsBackground,

                    Name = "BGShell",
                    Version = new Version(ProductVersion),

                    CurrentCulture = CultureInfo.CurrentCulture,
                    CurrentUICulture = CultureInfo.CurrentUICulture,
                };

                _runspaceProxy = RunspaceProxyFactory.CreateRunspace(AppDomain.CurrentDomain, _hostInfo, this, _privateObject);
                _runspaceProxy.OpenAsync();

                _writer.Enabled = true;
            }
        }

        public void CloseRunspaceAsync()
        {
            CurrentStatus = ConsoleControlStatus.ExitRequested;

            if (_runspaceProxy != null)
            {
                _runspaceProxy.CloseAsync();
            }
        }

        public override string ToString()
        {
            return WindowTitle;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override bool ProcessCmdKey(ref Message m, Keys keyData)
        {
            const Keys ControlPlus = (Keys.Control | Keys.Add);
            const Keys ControlPlusOem = (Keys.Control | Keys.Oemplus);

            const Keys ControlMinus = (Keys.Control | Keys.Subtract);
            const Keys ControlMinusOem = (Keys.Control | Keys.OemMinus);

            const Keys ControlA = (Keys.Control | Keys.A);
            const Keys ControlC = (Keys.Control | Keys.C);
            const Keys ControlV = (Keys.Control | Keys.V);
            const Keys ControlX = (Keys.Control | Keys.X);

            const Keys ControlInsert = (Keys.Control | Keys.Insert);
            const Keys ShiftDelete = (Keys.Shift | Keys.Delete);
            const Keys ShiftInsert = (Keys.Shift | Keys.Insert);

            if (keyData == ControlA)
            {
                SelectAll();
                return true;
            }

            if ((keyData == ControlPlus) || (keyData == ControlPlusOem))
            {
                ZoomFactor += 0.1f;
                return true;
            }

            if ((keyData == ControlMinus) || (keyData == ControlMinusOem))
            {
                if (ZoomFactor > 0.2f)
                {
                    ZoomFactor -= 0.1f;
                }

                return true;
            }

            if (Pipeline != null)
            {
                if ((keyData == ControlC) || (keyData == Keys.Escape))
                {
                    Pipeline.StopAsync();
                    return true;
                }
            }
            else
            {
                if ((keyData == ControlC) || (keyData == ControlInsert))
                {
                    Copy();
                    return true;
                }

                if ((keyData == ControlV) || (keyData == ShiftInsert))
                {
                    Paste();
                    return true;
                }

                if ((keyData == ControlX) || (keyData == ShiftDelete))
                {
                    Cut();
                    return true;
                }
            }

            return base.ProcessCmdKey(ref m, keyData);
        }

        [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
        protected override bool ProcessDialogKey(Keys keyData)
        {
            if (_reader != null)
            {
                if (_reader.ProcessDialogKey(keyData))
                {
                    return true;
                }
            }

            return base.ProcessDialogKey(keyData);
        }

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);

            using (AllowProtectedUpdates())
            {
                AppendText(BGShellApplicationInfo.Version + '\v' +
                           BGShellApplicationInfo.Copyright + "\v\v");

                ProtectAll();
            }
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            e.Handled = (CurrentStatus != ConsoleControlStatus.Ready);

            if (!e.Handled)
            {
                base.OnKeyPress(e);
            }
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            _bufferSize.Width = 0;
            base.OnSizeChanged(e);
        }

        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
        }

        internal ConsoleControlStatus CurrentStatus
        {
            get { return _currentStatus; }
            set
            {
                var prev = _currentStatus;

                if (prev != ConsoleControlStatus.ExitRequested)
                {
                    if (prev != value)
                    {
                        _currentStatus = value;

                        if ((prev == ConsoleControlStatus.Busy) ||
                            (value == ConsoleControlStatus.Busy))
                        {
                            OnIsBusyChanged(EventArgs.Empty);
                        }

                        if (_tipProvider != null)
                        {
                            _tipProvider.Enabled = (value == ConsoleControlStatus.Ready);
                        }
                    }
                }
            }
        }

        internal BGShellPipelineProxy Pipeline
        {
            get { return _pipelineProxy; }
            set 
            {
                if (_pipelineProxy != value)
                {
                    _pipelineProxy = value;

                    if (value != null)
                    {
                        CurrentStatus = ConsoleControlStatus.Busy;
                    }
                }
            }
        }

        internal IRunspaceProxy Runspace
        {
            get { return _runspaceProxy; }
        }

        #region KeyboardHook

        private KeyboardHook KeyboardHook
        {
            get
            {
                if (_hook == null)
                {
                    _hook = BGShellApplication.Current.KeyboardHook;
                    _hook.KeyDown += OnHookKeyDown;
                }

                return _hook;
            }
        }

        private void RegisterHookKeys(IEnumerable<Keys> keys)
        {
            if (KeyboardHook.ShortcutKeys.AddRange(keys))
            {
                KeyboardHook.UpdateHook();
            }
        }

        private void UnregisterHookKeys(IEnumerable<Keys> keys)
        {
            if (KeyboardHook.ShortcutKeys.RemoveRange(keys))
            {
                KeyboardHook.UpdateHook();
            }
        }

        private void OnHookKeyDown(object sender, KeyEventArgs e)
        {
            _runspaceProxy.InvokeHookKey(e.KeyData);
        }

        #endregion

        #region DataTips

        internal EditControlDataTip AddDataTip(RichTextRange range, DataTipItem tip)
        {
            if (_tipProvider != null)
            {
                return _tipProvider.AddItem(range, tip);
            }

            return null;
        }

        internal void RemoveDataTips(IEnumerable<EditControlDataTip> tips)
        {
            if (_tipProvider != null)
            {
                foreach (var tip in tips)
                {
            	    _tipProvider.RemoveItem(tip);
                }
            }
        }

        internal void ClearDataTips()
        {
            if (_tipProvider != null)
            {
                _tipProvider.ClearItems();
            }
        }

        internal void UpdateTipsEnabled()
        {
            if (_tipProvider != null)
            {
                _tipProvider.Enabled = !IsBusy;
            }
        }

        #endregion

        private void WritePrompt(string text)
        {
            _writer.Enabled = false;
            Pipeline = null;

            if (CurrentStatus != ConsoleControlStatus.ExitRequested)
            {
                var range = AppendText(text, FormatSettings.Normal);

                using (AllowProtectedUpdates())
                {
                    SetIndents(range);

                    FormatSettings.Normal.Apply(TextDocument.Selection.Font);
                }

                ReadLineAndInvoke();
            }
        }

        private static void SetIndents(RichTextRange promptRange)
        {
            int x = promptRange.EndPoint.X;
            int indent = PixelsToPointsX(x);

            promptRange.Paragraph.SetIndents(-indent, indent, 0);
        }

        private RichTextRange AppendText(string text, ConsoleControlFormatEntry format)
        {
            using (AllowProtectedUpdates())
            {
                return AppendTextInner(text, format);
            }
        }

        private RichTextRange AppendTextInner(string text, ConsoleControlFormatEntry format)
        {
            var first = TextLength;
            var range = TextDocument.GetRange(TextLength, TextLength);

            range.Text += text;

            if (format != null)
            {
                range = TextDocument.GetRange(first, first + text.Length);
                range.Font.Underline = RichTextUnderline.None;

                format.Apply(range.Font);
            }

            return range;
        }

        private void ProtectAll()
        {
            using (AllowProtectedUpdates())
            {
                TextDocument.DocumentRange.Font.Protected = true;
            }
        }

        private void ReadLineAndInvoke()
        {
            CheckNoReader();

            _reader = new InputReader(this);
        }

        /// <remarks>not to be called on the UI thread</remarks>
        private string ReadLine()
        {
            CheckNoReader();
            CheckNotUIThread();

            var sync = new Object();
            var reader = new InputReader(this, sync);

            _reader = reader;

            lock (sync)
            {
                Monitor.Wait(sync);
            }

            return reader.Text;
        }

        private void CheckNotUIThread()
        {
            if (!InvokeRequired)
            {
                throw new InvalidOperationException();
            }
        }

        private void CheckNoReader()
        {
            if (_reader != null)
            {
                throw new InvalidOperationException();
            }
        }

        #region InvokeIfRequired

        internal void BeginInvokeIfRequired(Action action)
        {
            if (action == null)
            {
                return;
            }

            if (InvokeRequired)
            {
                BeginInvoke(action);
            }
            else
            {
                action();
            }
        }

        internal void BeginInvokeIfRequired<T>(Action<T> action, T arg)
        {
            if (action == null)
            {
                return;
            }

            if (InvokeRequired)
            {
                BeginInvoke(action, arg);
            }
            else
            {
                action(arg);
            }
        }

        internal void BeginInvokeIfRequired<T, U>(Action<T, U> action, T a, U b)
        {
            if (action != null)
            {
                if (InvokeRequired)
                {
                    BeginInvoke(action, a, b);
                }
                else
                {
                    action(a, b);
                }
            }
        }

        internal void InvokeIfRequired(Action action)
        {
            if (InvokeRequired)
            {
                Invoke(action);
            }
            else
            {
                action();
            }
        }

        internal T InvokeIfRequired<T>(Func<T> func)
        {
            if (InvokeRequired)
            {
                return (T)Invoke(func);
            }
            else
            {
                return func();
            }
        }

        #endregion

        #region Closed

        public event EventHandler<ConsoleControlClosedEventArgs> Closed
        {
            add { Events.AddHandler(EventClosed, value); }
            remove { Events.RemoveHandler(EventClosed, value); }
        }

        protected virtual void OnClosed(ConsoleControlClosedEventArgs args)
        {
            var handler = Events[EventClosed] as EventHandler<ConsoleControlClosedEventArgs>;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        private static readonly object EventClosed = new object();

        #endregion

        #region IsBusyChanged

        public event EventHandler IsBusyChanged
        {
            add { Events.AddHandler(EventIsBusyChanged, value); }
            remove { Events.RemoveHandler(EventIsBusyChanged, value); }
        }

        protected virtual void OnIsBusyChanged(EventArgs args)
        {
            var handler = Events[EventIsBusyChanged] as EventHandler;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        private static readonly object EventIsBusyChanged = new object();

        #endregion

        #region WindowTitleChanged

        public event EventHandler WindowTitleChanged
        {
            add { Events.AddHandler(EventWindowTitleChanged, value); }
            remove { Events.RemoveHandler(EventWindowTitleChanged, value); }
        }

        protected virtual void OnWindowTitleChanged(EventArgs args)
        {
            var handler = Events[EventWindowTitleChanged] as EventHandler;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        private static readonly object EventWindowTitleChanged = new object();

        #endregion

        public bool CanCopy
        {
            get { return (SelectionLength > 0); }
        }

        public bool CanCut
        {
            get { return _reader != null && _reader.CanCut; }
        }

        public new bool CanPaste
        {
            get
            {
                if (_reader != null)
                {
                    return Clipboard.ContainsText();
                }

                return false;
            }
        }

        public new void Copy()
        {
            using (AllowProtectedUpdates())
            {
                // copying needs protection disabled, wtf?

                base.Copy();
            }
        }

        public new void Cut()
        {
            if (CanCut)
            {
                base.Cut();
            }
        }

        public new void Paste()
        {
            if (_reader != null)
            {
                _reader.Paste();
            }
        }

        public new void SelectAll()
        {
            TextDocument.Selection.SetRange(0, TextLength);
        }
    }
}